package org.jim.server.http;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.jim.common.http.Cookie;
import org.jim.common.http.HttpConfig;
import org.jim.common.http.HttpConst;
import org.jim.common.http.HttpRequest;
import org.jim.common.http.HttpResponse;
import org.jim.common.http.HttpResponseStatus;
import org.jim.common.http.RequestLine;
import org.jim.common.http.handler.IHttpRequestHandler;
import org.jim.common.http.listener.IHttpServerListener;
import org.jim.common.http.session.HttpSession;
import org.jim.server.http.mvc.Routes;
import org.jim.server.util.ClassUtils;
import org.jim.server.util.HttpResps;
import org.tio.utils.cache.guava.GuavaCache;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ClassUtil;

/**
 * @author WChao
 */
public class DefaultHttpRequestHandler implements IHttpRequestHandler {
    private static Logger log = LoggerFactory.getLogger(DefaultHttpRequestHandler.class);

    //	/**
    //	 * 静态资源的CacheName
    //	 * key:   path 譬如"/index.html"
    //	 * value: HttpResponse
    //	 */
    //	private static final String STATIC_RES_CACHENAME = "TIO_HTTP_STATIC_RES";

    /**
     * 静态资源的CacheName
     * key:   path 譬如"/index.html"
     * value: FileCache
     */
    private static final String STATIC_RES_CONTENT_CACHENAME = "TIO_HTTP_STATIC_RES_CONTENT";

    /**
     * @param args
     * @author WChao
     * 2016年11月18日 上午9:13:15
     */
    public static void main(String[] args) {
    }

    protected HttpConfig httpConfig;

    protected Routes routes = null;

    //	private LoadingCache<String, HttpSession> loadingCache = null;

    private IHttpServerListener httpServerListener;

    private GuavaCache staticResCache;

    /**
     * @param httpConfig
     * @author WChao
     */
    public DefaultHttpRequestHandler(HttpConfig httpConfig) {
        this.httpConfig = httpConfig;

        if (httpConfig.getMaxLiveTimeOfStaticRes() > 0) {
            //			GuavaCache.register(STATIC_RES_CACHENAME, (long) httpConfig.getMaxLiveTimeOfStaticRes(), null);
            staticResCache = GuavaCache.register(STATIC_RES_CONTENT_CACHENAME, (long) httpConfig.getMaxLiveTimeOfStaticRes(), null);
        }
        this.setHttpServerListener(httpConfig.getHttpServerListener());
    }

    /**
     * @param httpConfig
     * @param routes
     * @author WChao
     */
    public DefaultHttpRequestHandler(HttpConfig httpConfig, Routes routes) {
        this(httpConfig);
        this.routes = routes;
    }

    /**
     * 创建httpsession
     *
     * @return
     * @author WChao
     */
    private HttpSession createSession() {
        String sessionId = httpConfig.getSessionIdGenerator().sessionId(httpConfig);
        HttpSession httpSession = new HttpSession(sessionId);
        return httpSession;
    }

    /**
     * @return the httpConfig
     */
    public HttpConfig getHttpConfig() {
        return httpConfig;
    }

    public IHttpServerListener getHttpServerListener() {
        return httpServerListener;
    }

    private Cookie getSessionCookie(HttpRequest request, HttpConfig httpConfig) throws ExecutionException {
        Cookie sessionCookie = request.getCookie(httpConfig.getSessionCookieName());
        return sessionCookie;
    }

    /**
     * @return the staticResCache
     */
    public GuavaCache getStaticResCache() {
        return staticResCache;
    }

    @Override
    public HttpResponse handler(HttpRequest request, RequestLine requestLine) throws Exception {
        HttpResponse ret = null;
        try {
            processCookieBeforeHandler(request, requestLine);
            HttpSession httpSession = request.getHttpSession();//(HttpSession) channelContext.getAttribute();

            if (httpServerListener != null) {
                ret = httpServerListener.doBeforeHandler(request, requestLine, ret);
                if (ret != null) {
                    return ret;
                }
            }

            String path = requestLine.getPath();
            String initPath = path;

            Method method = routes.pathMethodMap.get(initPath);
            if (method != null) {
                String[] paramNames = routes.methodParamnameMap.get(method);
                Class<?>[] parameterTypes = method.getParameterTypes();

                Object bean = routes.methodBeanMap.get(method);
                Object obj = null;
                Map<String, Object[]> params = request.getParams();
                if (parameterTypes == null || parameterTypes.length == 0) {
                    obj = method.invoke(bean);
                } else {
                    //赋值这段代码待重构，先用上
                    Object[] paramValues = new Object[parameterTypes.length];
                    int i = 0;
                    for (Class<?> paramType : parameterTypes) {
                        try {
                            if (paramType.isAssignableFrom(HttpRequest.class)) {
                                paramValues[i] = request;
                            } else if (paramType == HttpSession.class) {
                                paramValues[i] = httpSession;
                            } else if (paramType.isAssignableFrom(HttpConfig.class)) {
                                paramValues[i] = httpConfig;
                            } else if (paramType.isAssignableFrom(ChannelContext.class)) {
                                paramValues[i] = request.getChannelContext();
                            } else {
                                if (params != null) {
                                    if (ClassUtils.isSimpleTypeOrArray(paramType)) {
                                        Object[] value = params.get(paramNames[i]);
                                        if (value != null && value.length > 0) {
                                            if (paramType.isArray()) {
                                                paramValues[i] = Convert.convert(paramType, value);
                                            } else {
                                                paramValues[i] = Convert.convert(paramType, value[0]);
                                            }
                                        }
                                    } else {
                                        paramValues[i] = paramType.newInstance();//BeanUtil.mapToBean(params, paramType, true);
                                        Set<Entry<String, Object[]>> set = params.entrySet();
                                        label2:
                                        for (Entry<String, Object[]> entry : set) {
                                            String fieldName = entry.getKey();
                                            Object[] fieldValue = entry.getValue();

                                            PropertyDescriptor propertyDescriptor = BeanUtil.getPropertyDescriptor(paramType, fieldName, true);
                                            if (propertyDescriptor == null) {
                                                continue label2;
                                            } else {
                                                Method writeMethod = propertyDescriptor.getWriteMethod();
                                                if (writeMethod == null) {
                                                    continue label2;
                                                }
                                                writeMethod = ClassUtil.setAccessible(writeMethod);
                                                Class<?>[] clazzes = writeMethod.getParameterTypes();
                                                if (clazzes == null || clazzes.length != 1) {
                                                    log.info("方法的参数长度不为1，{}.{}", paramType.getName(), writeMethod.getName());
                                                    continue label2;
                                                }
                                                Class<?> clazz = clazzes[0];

                                                if (ClassUtils.isSimpleTypeOrArray(clazz)) {
                                                    if (fieldValue != null && fieldValue.length > 0) {
                                                        if (clazz.isArray()) {
                                                            writeMethod.invoke(paramValues[i], Convert.convert(clazz, fieldValue));
                                                        } else {
                                                            writeMethod.invoke(paramValues[i], Convert.convert(clazz, fieldValue[0]));
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } catch (Exception e) {
                            log.error(e.toString(), e);
                        } finally {
                            i++;
                        }
                    }
                    obj = method.invoke(bean, paramValues);
                }

                if (obj instanceof HttpResponse) {
                    ret = (HttpResponse) obj;
                    return ret;
                } else {
                    throw new Exception(bean.getClass().getName() + "#" + method.getName() + "返回的对象不是" + HttpResponse.class.getName());
                }
            } else {
                GuavaCache contentCache = null;
                FileCache fileCache = null;
                if (httpConfig.getMaxLiveTimeOfStaticRes() > 0) {
                    contentCache = GuavaCache.getCache(STATIC_RES_CONTENT_CACHENAME);
                    fileCache = (FileCache) contentCache.get(initPath);
                }
                if (fileCache != null) {
                    byte[] bodyBytes = fileCache.getData();
                    Map<String, String> headers = fileCache.getHeaders();
                    long lastModified = fileCache.getLastModified();
                    log.info("从缓存获取:[{}], {}", path, bodyBytes.length);

                    ret = HttpResps.try304(request, lastModified);
                    if (ret != null) {
                        ret.addHeader(HttpConst.ResponseHeaderKey.tio_from_cache, "true");

                        return ret;
                    }

                    ret = new HttpResponse(request, httpConfig);
                    ret.setBody(bodyBytes, request);
                    ret.addHeaders(headers);
                    return ret;
                } else {
                    String root = FileUtil.getAbsolutePath(httpConfig.getPageRoot());
                    File file = new File(root + path);
                    if (!file.exists() || file.isDirectory()) {
                        if (StringUtils.endsWith(path, "/")) {
                            path = path + "index.html";
                        } else {
                            path = path + "/index.html";
                        }
                        file = new File(root, path);
                    }

                    if (file.exists()) {
                        ret = HttpResps.file(request, file);
                        ret.setStaticRes(true);

                        if (contentCache != null && request.getIsSupportGzip()) {
                            if (ret.getBody() != null && ret.getStatus() == HttpResponseStatus.C200) {
                                String contentType = ret.getHeader(HttpConst.ResponseHeaderKey.Content_Type);
                                String contentEncoding = ret.getHeader(HttpConst.ResponseHeaderKey.Content_Encoding);
                                String lastModified = ret.getHeader(HttpConst.ResponseHeaderKey.Last_Modified);

                                Map<String, String> headers = new HashMap<>();
                                if (StringUtils.isNotBlank(contentType)) {
                                    headers.put(HttpConst.ResponseHeaderKey.Content_Type, contentType);
                                }
                                if (StringUtils.isNotBlank(contentEncoding)) {
                                    headers.put(HttpConst.ResponseHeaderKey.Content_Encoding, contentEncoding);
                                }
                                if (StringUtils.isNotBlank(lastModified)) {
                                    headers.put(HttpConst.ResponseHeaderKey.Last_Modified, lastModified);
                                }
                                headers.put(HttpConst.ResponseHeaderKey.tio_from_cache, "true");

                                fileCache = new FileCache(headers, file.lastModified(), ret.getBody());
                                contentCache.put(initPath, fileCache);
                                log.info("放入缓存:[{}], {}", initPath, ret.getBody().length);
                            }
                        }

                        return ret;
                    }
                }
            }

            ret = resp404(request, requestLine);//Resps.html(request, "404--并没有找到你想要的内容", httpConfig.getCharset());
            return ret;
        } catch (Exception e) {
            logError(request, requestLine, e);
            ret = resp500(request, requestLine, e);//Resps.html(request, "500--服务器出了点故障", httpConfig.getCharset());
            return ret;
        } finally {
            if (ret != null) {
                try {
                    processCookieAfterHandler(request, requestLine, ret);
                    if (httpServerListener != null) {
                        httpServerListener.doAfterHandler(request, requestLine, ret);
                    }
                } catch (Exception e) {
                    logError(request, requestLine, e);
                }
            }
        }
    }

    private void logError(HttpRequest request, RequestLine requestLine, Exception e) {
        StringBuilder sb = new StringBuilder();
        sb.append("\r\n").append("remote  :").append(request.getRemote());
        sb.append("\r\n").append("request :").append(requestLine.getLine());
        log.error(sb.toString(), e);

    }

    private void processCookieAfterHandler(HttpRequest request, RequestLine requestLine, HttpResponse httpResponse) throws ExecutionException {
        HttpSession httpSession = request.getHttpSession();//(HttpSession) channelContext.getAttribute();//.getHttpSession();//not null
        Cookie cookie = getSessionCookie(request, httpConfig);
        String sessionId = null;

        if (cookie == null) {
            String domain = request.getHeader(HttpConst.RequestHeaderKey.Host);
            String name = httpConfig.getSessionCookieName();
            long maxAge = httpConfig.getSessionTimeout();
            //			maxAge = Integer.MAX_VALUE; //把过期时间掌握在服务器端

            sessionId = httpSession.getId();//randomCookieValue();

            cookie = new Cookie(domain, name, sessionId, maxAge);
            httpResponse.addCookie(cookie);
            httpConfig.getSessionStore().put(sessionId, httpSession);
            log.info("{} 创建会话Cookie, {}", request.getChannelContext(), cookie);
        } else {
            sessionId = cookie.getValue();
            HttpSession httpSession1 = (HttpSession) httpConfig.getSessionStore().get(sessionId);

            if (httpSession1 == null) {//有cookie但是超时了
                sessionId = httpSession.getId();
                String domain = request.getHeader(HttpConst.RequestHeaderKey.Host);
                String name = httpConfig.getSessionCookieName();
                long maxAge = httpConfig.getSessionTimeout();
                //				maxAge = Long.MAX_VALUE; //把过期时间掌握在服务器端

                cookie = new Cookie(domain, name, sessionId, maxAge);
                httpResponse.addCookie(cookie);

                httpConfig.getSessionStore().put(sessionId, httpSession);
            }
        }
    }

    private void processCookieBeforeHandler(HttpRequest request, RequestLine requestLine) throws ExecutionException {
        Cookie cookie = getSessionCookie(request, httpConfig);
        HttpSession httpSession = null;
        if (cookie == null) {
            httpSession = createSession();
        } else {
            String sessionId = cookie.getValue();
            httpSession = (HttpSession) httpConfig.getSessionStore().get(sessionId);
            if (httpSession == null) {
                log.info("{} session【{}】超时", request.getChannelContext(), sessionId);
                httpSession = createSession();
            }
        }
        request.setHttpSession(httpSession);
    }

    @Override
    public HttpResponse resp404(HttpRequest request, RequestLine requestLine) {
        String file404 = httpConfig.getPage404();
        String root = FileUtil.getAbsolutePath(httpConfig.getPageRoot());
        File file = new File(root + file404);
        if (file.exists()) {
            HttpResponse ret = HttpResps.redirect(request, file404 + "?tio_initpath=" + requestLine.getPathAndQuery());
            return ret;
        } else {
            HttpResponse ret = HttpResps.html(request, "404");
            return ret;
        }
    }

    @Override
    public HttpResponse resp500(HttpRequest request, RequestLine requestLine, Throwable throwable) {
        String file500 = httpConfig.getPage500();
        String root = FileUtil.getAbsolutePath(httpConfig.getPageRoot());
        File file = new File(root + file500);
        if (file.exists()) {
            HttpResponse ret = HttpResps.redirect(request, file500 + "?tio_initpath=" + requestLine.getPathAndQuery());
            return ret;
        } else {
            HttpResponse ret = HttpResps.html(request, "500");
            return ret;
        }
    }

    /**
     * @param httpConfig the httpConfig to set
     */
    public void setHttpConfig(HttpConfig httpConfig) {
        this.httpConfig = httpConfig;
    }

    public void setHttpServerListener(IHttpServerListener httpServerListener) {
        this.httpServerListener = httpServerListener;
    }

    /**
     * @param staticResCache the staticResCache to set
     */
    public void setStaticResCache(GuavaCache staticResCache) {
        this.staticResCache = staticResCache;
    }

    @Override
    public void clearStaticResCache(HttpRequest request) {
        if (staticResCache != null) {
            staticResCache.clear();
        }
    }

}
